package main

import (
	"errors"
	"fmt"
	"github.com/go-basic/uuid"
	_ "github.com/go-basic/uuid"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
	"time"
)

// 文章
type Topics struct {
	Id         string     `gorm:"primary_key"`
	Title      string     `gorm:"not null"`
	UserId     int        `gorm:"not null"`
	CategoryId int        `gorm:"not null"`
	Category   Categories `gorm:"foreignkey:CategoryId"` //文章所属分类外键
	User       Users1     `gorm:"foreignkey:UserId"`     //文章所属用户外键
}

// 用户
type Users1 struct {
	Id   string `gorm:"primary_key"`
	Name string `gorm:"not null"`
}

// 分类
type Categories struct {
	Id   string `gorm:"primary_key"`
	Name string `gorm:"not null"`
}

func GetDB() *gorm.DB {

	//db1, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
	db1, err := gorm.Open("mysql", "root:123@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local")
	db1.SingularTable(true)
	// SetMaxIdleConns 设置空闲连接池中的最大连接数。
	db1.DB().SetMaxIdleConns(10)
	//SetMaxOpenConns 设置数据库连接最大打开数。
	db1.DB().SetMaxOpenConns(100)
	// SetConnMaxLifetime 设置可重用连接的最长时间
	db1.DB().SetConnMaxLifetime(time.Hour)

	if err != nil {
		fmt.Println("db error:", err)
	} else {
		fmt.Println("database connection success")
	}
	return db1
}

func main() {
	db := GetDB()
	model := []interface{}{
		&Topics{},
		&Users1{},
		&Categories{},
	}
	//1.执行建表语句
	//AutoMigrate 自动迁移
	//警告：migrate 仅支持创建表、增加表中没有的字段和索引。为了保护你的数据，它并不支持改变已有的字段类型或删除未被使用的字段
	err := db.Debug().AutoMigrate(model...).Error
	// 检查模型中 User 表是否存在
	db.Debug().HasTable(model)
	// 通过模型 User 创建表
	db.Debug().Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(model...)
	// 删除模型 User 表
	db.Debug().DropTable(&Users1{})
	// 删除模型 User 表和 products 表
	db.Debug().DropTableIfExists(&Users1{}, "products")
	// User 模型，改变 description 字段的数据类型为 `text`
	db.Model(&Users1{}).Debug().ModifyColumn("description", "text")
	// User 模型，删除  description 字段
	db.Model(&Users1{}).Debug().DropColumn("description")
	/*
		新增索引
	*/
	// 为 `name` 字段建立一个名叫 `idx_user_name` 的索引
	//db.Model(&Users1{}).Debug().AddIndex("idx_user_name", "name")
	//db.Model(&Users1{}).Debug().AddUniqueIndex("idx_user_name", "name")
	//移除索引
	//db.Model(&Users1{}).Debug().RemoveIndex("idx_user_name")
	// 添加主键
	// 第一个参数 : 主键的字段
	// 第二个参数 : 目标表的 ID
	// 第三个参数 : ONDELETE
	// 第四个参数 : ONUPDATE
	db.Model(&Users1{}).Debug().AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
	db.Model(&Users1{}).Debug().RemoveForeignKey("city_id", "cities(id)")

	if err != nil {
		fmt.Println("db error:", err)
	}
	//3.执行预加载
	topics, err := GetTopicsById(db, 1)
	if err != nil {
		fmt.Println("get topics error:", err)
	}
	fmt.Println(topics)

}

func GetTopicsById(db *gorm.DB, id int) (*Topics, error) {
	var topic Topics
	err := db.Debug().Where("id=?", id).
		Preload("Category").
		Preload("User").
		First(&topic).Error

	uuid, _ := uuid.GenerateUUID()
	db.Debug().Create(&Users1{
		Id:   uuid,
		Name: "haungyuzhou",
	})
	var users1 Users1
	db.Debug().Find(&users1, "name=?", "haungyuzhou")
	db.Debug().Model(&users1).Where("id =?", "5c8d5acc-4792-3f86-386d-60e9dbab7c74").UpdateColumn("name", "lisi")

	db.Debug().Model(&users1).Select("COALESCE(id,?)", 42).Rows()
	//链式操作
	tx := db.Debug().Model(&users1).Where("name =?", "lisi")
	someCondition := true
	if someCondition {
		tx.Where("'id =?", "5c8d5acc-4792-3f86-386d-60e9dbab7c74")
	}
	tx = db.Begin()
	if result := db.Debug().Scopes(AmountGreaterThan1000).Find(&users1); result.Error != nil {
		tx.Rollback()
	}

	if result := db.Debug().Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&users1); len(result.GetErrors()) > 0 {
		fmt.Println("1111")
	}

	/**
	引载 ：https://www.cnblogs.com/lvpengbo/p/13705913.html
	执行原生SQL时不能使用链式调用其他方法
	*/
	resultt := db.Debug().Exec("select id from Users1").Value
	fmt.Println(resultt)

	row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
	row.Scan(&users1)
	tx.Commit()

	//rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
	//defer rows.Close()
	//for rows.Next() {
	//	rows.Scan(&name, &age, &email)
	//}
	//
	//// 原生SQL
	//rows1, err1 := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)
	//defer rows1.Close()
	//for rows1.Next() {
	//	rows1.Scan(&name, &age, &email)
	//}

	//db.Debug().Where("name =?", "haungyuzhou").Delete(Users1{})
	//tx := db.Debug().Where("name =?","jinzhu")
	//tx = tx.Where("active = ?", 1)
	// 新增更多的筛选条件
	/*if someCondition {
		tx = tx.Where("age = ?", 20)
	} else {
		tx = tx.Where("age = ?", 30)
	}*/

	if err != nil {
		return nil, err
	}
	return &topic, nil
}

//
//Scopes 方法
//链路操作
//所有的链式操作都将会克隆并创建一个新的数据库对象（共享一个连接池），GORM 对于多个 goroutines 的并发使用是安全的。
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
	if err := db.Debug().Where("amount > ?", 1000).Error; gorm.IsRecordNotFoundError(err) {
		fmt.Println(err)
	}
	return db.Debug().Where("amount > ?", 1000)
}
func OrderStatus(status []string) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.Debug().Scopes(AmountGreaterThan1000).Where("status in (?)", status)
	}

}

/**
钩子函数 （钩子只能在事务中生效）
1、在 GORM 中的保存 / 删除 操作会默认进行事务处理，所以在事物中，所有的改变都是无效的，直到它被提交为止:
2、GORM 默认在事务中执行单个 create， update， delete 操作，以确保数据库数据完整性。
如果需要将多个 create， update， delete 当成一个原子性操作，Transaction 就是为了这个而创造的。
*/
func (u *Users1) BeforeSave() (err error) {
	if u.Name == "" {
		err = errors.New("can't save invalid data")
	}
	return
}

/**
事务机制
*/

func CreateAnimals(db gorm.DB) (err []error) {
	var users1 Users1

	//开启事务
	tx := db.Begin()
	if err = db.Debug().Where("name =?", "lisi").Find(&users1).GetErrors(); err != nil {
		for i := 0; i < len(err); i++ {
			fmt.Println(err)
		}
		tx.Rollback()
	}
	tx.Commit()

	return
}
